package com.ljkj.cordial.lengthtcpheartbeat;

import android.text.TextUtils;

import com.lanjiu.lib.communication.IMSConfig;
import com.lanjiu.lib.utils.log.Logger;
import com.lanjiu.pro.business.BusinessProtocolMessageStandard;
import com.nic.view.util.JLog;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public abstract class OnEventMessageListener implements IEventMessageListener {
    public final static String TAG = TcpLengthHeartbeatClient.class.getSimpleName();
    private AtomicInteger failTimes = new AtomicInteger(0);
    private HashedWheelTimer wheelTimer = new HashedWheelTimer();
    private String serverSendReportMsgType;
    private String clientReceivedReportMsgType;
    private boolean isSuccessWriting;
    private long heartbeatSpaceTime;
    private boolean isFailingWriting;
    private boolean isReConnecting;
    private ChannelHandlerContext ctx;
    private String userId;


    /**
     * 是否开启成功计时
     */
    private boolean isRequestSuccessAtomic;
    private OnConnectStateCallback onConnectStateCallback;

    private String serverSendReportCheckSum;

    private String clientReceivedReportCheckSum;

    public void setCtx(ChannelHandlerContext ctx, OnConnectStateCallback onConnectStateCallback) {
        this.ctx = ctx;
        this.onConnectStateCallback = onConnectStateCallback;
        setReConnecting(false);
        setHeartbeatSpaceTime(27);
        setRequestSuccessAtomic(true);
        setFailingWriting(false);
        setSuccessWriting(false);
        failTimes.set(0);
//        successTimes = new AtomicInteger(0);
    }

    //    @Override
//    public BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage getHeartbeatMsg() {
//        BusinessProtocolMessageStandard.Head header = BusinessProtocolMessageStandard.Head.newBuilder()
//                .setFromId(TextUtils.isEmpty(getUserId()) ? IpAddressUtil.getNewMac() : getUserId()).setMsgType(IMSConfig.HEART_PACKAGE_PHONE).build();
//        BusinessProtocolMessageStandard.UnifiedEntranceMessage message = BusinessProtocolMessageStandard.UnifiedEntranceMessage.newBuilder().setHead(header).build();
//        return BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage.newBuilder()
//                .setUnifiedEntranceMessage(message).build();
//    }
//
//    @Override
//    public BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage getDisconHeartbeatMsg() {
//        BusinessProtocolMessageStandard.Head header = BusinessProtocolMessageStandard.Head.newBuilder()
//                .setFromId(TextUtils.isEmpty(getUserId()) ? IpAddressUtil.getNewMac() : getUserId()).setMsgType(IMSConfig.HEART_PACKAGE_PHONE_END).build();
//        BusinessProtocolMessageStandard.UnifiedEntranceMessage message = BusinessProtocolMessageStandard.UnifiedEntranceMessage.newBuilder().setHead(header).build();
//        return BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage.newBuilder()
//                .setUnifiedEntranceMessage(message).build();
//    }
    @Override
    public BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage getEmptyMsg(String msgType) {
        BusinessProtocolMessageStandard.Head head;
        if (IMSConfig.HEART_PACKAGE_PHONE.equals(msgType) || IMSConfig.HEART_PACKAGE_PHONE_END.equals(msgType)) {
            head = BusinessProtocolMessageStandard.Head.newBuilder()
                    .setMsgType(msgType)
                    .setFromId(TextUtils.isEmpty(getUserId()) ? IpAddressUtil.getNewMac() : getUserId())
                    .setToken(IpAddressUtil.getNewMac())
                    .build();
        } else {
            head = BusinessProtocolMessageStandard.Head.newBuilder()
                    .setMsgType(msgType)
                    .setFromId(TextUtils.isEmpty(getUserId()) ? IpAddressUtil.getNewMac() : getUserId())
                    .build();
        }

        return BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage.newBuilder()
                .setUnifiedEntranceMessage(BusinessProtocolMessageStandard.UnifiedEntranceMessage.newBuilder()
                        .setHead(head)
                        .build()).build();
    }

    public void setRequestSuccessAtomic(boolean requestSuccessAtomic) {
        isRequestSuccessAtomic = requestSuccessAtomic;
    }

    public boolean isRequestSuccessAtomic() {
        return isRequestSuccessAtomic;
    }

    public void setReConnecting(boolean reConnecting) {
        isReConnecting = reConnecting;
    }


    @Override
    public String getServerSendReportMsgType() {
        return serverSendReportMsgType;
    }

    @Override
    public String getClientReceivedReportMsgType() {
        return clientReceivedReportMsgType;
    }

    public void setServerSendReportCheckSum(String serverSendReportCheckSum) {
        this.serverSendReportCheckSum = serverSendReportCheckSum;
    }

    public void setClientReceivedReportCheckSum(String clientReceivedReportCheckSum) {
        this.clientReceivedReportCheckSum = clientReceivedReportCheckSum;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public String getUserId() {
        return userId;
    }

    @Override
    public String getServerSendReportCheckSum() {
        return serverSendReportCheckSum;
    }

    @Override
    public String getClientReceivedReportCheckSum() {
        return clientReceivedReportCheckSum;
    }

    @Override
    public boolean isSuccessWriting() {
        return isSuccessWriting;
    }

    @Override
    public int writSuccessedTimes() {
        return 0;
    }


    @Override
    public boolean isFailingWriting() {
        return isFailingWriting;
    }

    @Override
    public int writFailedTimes() {
        return failTimes.intValue();
    }

    @Override
    public long getHeartbeatSpaceTime() {
        return heartbeatSpaceTime;
    }

    @Override
    public boolean isReConnecting() {
        return isReConnecting;
    }

    public void setServerSendReportMsgType(String serverSendReportMsgType) {
        this.serverSendReportMsgType = serverSendReportMsgType;
    }

    public void setClientReceivedReportMsgType(String clientReceivedReportMsgType) {
        this.clientReceivedReportMsgType = clientReceivedReportMsgType;
    }

    public void setSuccessWriting(boolean successWriting) {
        isSuccessWriting = successWriting;
    }

    public void setHeartbeatSpaceTime(long heartbeatSpaceTime) {
        this.heartbeatSpaceTime = heartbeatSpaceTime;
    }

    public void setFailingWriting(boolean failingWriting) {
        isFailingWriting = failingWriting;
    }

    @Override
    public ChannelHandlerContext getChannelCxt() {
        return ctx;
    }

    public void setCtx(ChannelHandlerContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public long getOutTimeHeartbeatTime() {
        return 15 * 1000;
    }


    @Override
    public void send(BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage msg) {
        if (isReConnecting || isFailingWriting) {
            return;
        }
        if (getChannelCxt() != null && getChannelCxt().channel().isActive() && getChannelCxt().channel().isWritable()) {
            if (msg != null) {
                setSuccessWriting(true);
                Channel channel = getChannelCxt().channel();
                channel.eventLoop().submit(new Runnable() {
                    @Override
                    public void run() {
                        JLog.d(TAG, "[" + channel.id().toString() + "]" + "sending msg to the server...");
                        channel.writeAndFlush(msg).addListener(new GenericFutureListener() {
                            @Override
                            public void operationComplete(Future future) throws Exception {
                                setOverTimeLli(channel);
                            }
                        });
                    }
                });

            }
        } else {
            failTimes.set(0);
            setSuccessWriting(false);
            setFailingWriting(false);
            setRequestSuccessAtomic(false);
            if (onConnectStateCallback != null) {
                onConnectStateCallback.onDoConnect(getChannelCxt());
            }
        }
    }

    /**
     * 发送业务消息
     *
     * @param msg
     */
    public void sendBusiness(BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage msg) {
        if (getChannelCxt() != null && msg != null) {
            Channel channel = getChannelCxt().channel();
            if (channel.isActive() && channel.isWritable()) {
                channel.eventLoop().submit(new Runnable() {
                    @Override
                    public void run() {
                        getChannelCxt().channel().writeAndFlush(msg).addListener(new GenericFutureListener() {
                            @Override
                            public void operationComplete(Future future) throws Exception {
                                JLog.d(TAG, "send business msg->" + msg.getUnifiedEntranceMessage().getHead().getMsgType());
                            }
                        });
                    }
                });
            }
        }
    }

    /**
     * 超时监听
     *
     * @param channel
     */
    @Override
    public void setOverTimeLli(Channel channel) {
        if (!channel.isActive()) {
            failTimes.set(0);
            setSuccessWriting(false);
            setFailingWriting(false);
            setRequestSuccessAtomic(false);
            if (onConnectStateCallback != null) {
                onConnectStateCallback.onDoConnect(getChannelCxt());
            }
            return;
        }
        wheelTimer.newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                timeout.cancel();
                if (isSuccessWriting) {
                    JLog.d(TAG, "send msg overtime");
                    receiveError();
                }
            }
        }, getOutTimeHeartbeatTime(), TimeUnit.SECONDS);
    }

    @Override
    public void receiveMsg(BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage msg) {
        BusinessProtocolMessageStandard.UnifiedEntranceMessage message = msg.getUnifiedEntranceMessage();
        BusinessProtocolMessageStandard.Head head = message.getHead();
        String msgType = head.getMsgType();
        String fromId = head.getFromId();
        String toId = head.getToId();
        setClientReceivedReportMsgType(msgType);
        if (IMSConfig.REPLY_NOTICE_ACCEPT_SUCCESS.equals(msgType)) {
            JLog.d(TAG, "send heartbeat answer reply" + fromId + "," + toId);
            //心跳消息回执
            getChannelCxt().channel().writeAndFlush(msg.toBuilder().setUnifiedEntranceMessage(msg.getUnifiedEntranceMessage().toBuilder().setHead(msg.getUnifiedEntranceMessage().getHead().toBuilder()
                    .setFromId(toId).setToId(fromId).build())));
        } else if (IMSConfig.HEART_PACKAGE_PHONE.equals(msgType)) {
            JLog.d(TAG, "receive heartbeat answer->" + fromId + ">" + msgType);
            failTimes.set(0);
            setSuccessWriting(false);
            setFailingWriting(false);
            onConnectStateCallback.receiveHeartbeat(fromId);
        } else if (IMSConfig.HEART_PACKAGE_PHONE_END.equals(msgType)) {
            JLog.d(TAG, "do end heartbeat answer->" + msgType);
            onConnectStateCallback.onDoConnect(getChannelCxt());
        } else {
            JLog.d(TAG, "receive data->" + msg);
            doSendReceiptBusinessProtocol(getChannelCxt().channel(), head);
            if (!TextUtils.isEmpty(head.getFromId())) {
                setSuccessWriting(false);
                setFailingWriting(false);
                setHeartbeatSpaceTime(27);
                failTimes.set(0);
                onConnectStateCallback.onRead(msg);
            }
        }
    }

    /**
     * 秒数不准
     * 60s或者27s失败1次
     * 20s失败>2次
     * 10s失败>2次
     */
    @Override
    public void receiveError() {
        if (isReConnecting()) {
            return;
        }
        if (!getChannelCxt().channel().isActive() || !getChannelCxt().channel().isWritable()) {
            failTimes.set(0);
            setSuccessWriting(false);
            setFailingWriting(false);
            setRequestSuccessAtomic(false);
            if (onConnectStateCallback != null) {
                onConnectStateCallback.onDoConnect(getChannelCxt());
            }
            return;
        }
        setSuccessWriting(false);
        setFailingWriting(true);
        setRequestSuccessAtomic(false);
        if (getHeartbeatSpaceTime() == 27 && failTimes.intValue() >= 1) {
            setHeartbeatSpaceTime(10);
        } else if (getHeartbeatSpaceTime() == 10 && failTimes.intValue() >= 3) {
            if (onConnectStateCallback != null) {
                onConnectStateCallback.onDoConnect(getChannelCxt());
                return;
            }
        }
        failTimes.getAndIncrement();
        getChannelCxt().channel().eventLoop().submit(new Runnable() {
            @Override
            public void run() {
                JLog.d(TAG, "sending short space msg to the server..." + getHeartbeatSpaceTime() + "," + failTimes.intValue());
                getChannelCxt().channel().writeAndFlush(getEmptyMsg(IMSConfig.HEART_PACKAGE_PHONE));
            }
        });
        wheelTimer.newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                timeout.cancel();
                if (isFailingWriting) {
                    receiveError();
                }
            }
        }, getHeartbeatSpaceTime(), TimeUnit.SECONDS);
    }

    private void doSendReceiptBusinessProtocol(Channel channel, BusinessProtocolMessageStandard.Head head) {
        BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage receipt = generateReceiptBusinessProtocolMessageStandard(head);
        Logger.print("长连接推送回执", receipt);
        getChannelCxt().channel().writeAndFlush(receipt);
    }

    private BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage generateReceiptBusinessProtocolMessageStandard(BusinessProtocolMessageStandard.Head head) {
        BusinessProtocolMessageStandard.UnifiedEntranceMessage msg = BusinessProtocolMessageStandard.UnifiedEntranceMessage.newBuilder()
                .setHead(BusinessProtocolMessageStandard.Head.newBuilder()
                        .setMsgType(head.getMsgType())
                        .setMsgContentType(head.getMsgContentType())
                        .setFromId(head.getFromId())
                        .setToId(head.getToId())
                        .setTimestamp(head.getTimestamp())
                        .setStatusReport(IMSConfig.STATUS_REPORT_RECEIPT)
                        .setUniqueIdentify(head.getUniqueIdentify())
                        .setToken(head.getToken())
                        .build())
                .build();
        return BusinessProtocolMessageStandard.CheckUnifiedEntranceMessage.newBuilder().setUnifiedEntranceMessage(msg).build();
    }
}
